package com.test.filter;

import com.test.entity.Const;
import com.test.entity.RestBean;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
@Order(Const.ORDER_FlOW)
public class FlowLimitFilter extends HttpFilter {
    @Resource
    StringRedisTemplate template;
    /**
     * 执行过滤操作
     *
     * @param request 请求对象，用于获取请求信息
     * @param response 响应对象，用于向客户端返回数据
     * @param chain 过滤链，用于将请求传递给下一个过滤器或目标资源
     * @throws IOException 如果在执行过程中发生I/O错误
     * @throws ServletException 如果在执行过程中发生Servlet错误
     */
    @Override
    public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 获取客户端IP地址
        String ip = request.getRemoteAddr();

        // 检查IP地址的访问次数是否超过限制
        if(tryCount(ip)){
            // 获取请求的URL
            String url = request.getRequestURI();
            // 记录访问日志
            log.info( "访问了" + url+"接口");

            // 如果访问次数未超过限制，继续执行请求
            chain.doFilter(request,response);
        }
        else {
            // 如果访问次数超过限制，发送阻止访问的消息给客户端
            this.writeBlockMessage(response);
        }
    }

    /**
     * 向HttpServletResponse中写入禁止访问的响应信息
     * 此方法用于处理访问过于频繁的情况，通过设置HTTP响应的编码、类型以及主体内容，
     * 向客户端传达当前请求已被禁止的信息
     *
     * @param response HttpServletResponse对象，用于设置响应的编码、类型及写入响应体
     * @throws IOException 当写入响应体过程中发生I/O错误时抛出
     */
    private void writeBlockMessage(HttpServletResponse response) throws IOException {
        // 设置响应内容的字符编码，确保中文字符可以正确显示
        response.setCharacterEncoding("UTF-8");
        // 设置响应的内容类型为JSON，这是RESTful API常用的响应格式
        response.setContentType("application/json");
        // 写入禁止访问的JSON响应信息，告知客户端访问已被禁止的原因
        response.getWriter().write(RestBean.failure(403,"访问过于频繁,已经被禁止").asJsonString());
    }
    /**
     * 尝试计数IP地址的请求次数
     * 此方法用于同步控制IP地址的请求次数，以防止过多的请求超出限制
     *
     * @param ip IP地址，用于标识请求来源
     * @return 如果IP地址的请求次数已经达到限制，则返回false；否则，检查限制并返回结果
     */
    private boolean tryCount(String ip){
       // 使用String intern方法确保相同的IP地址字符串共享同一个实例，以减少内存消耗
       synchronized (ip.intern()){
           // 检查是否已经存在该IP的请求限制标识
           if(Boolean.TRUE.equals(template.hasKey(Const.FLOW_BLOCK + ip))){
               // 如果存在请求限制标识，表明请求已经被阻止，返回false
               return false;
           }
           // 如果不存在请求限制标识，调用限制检查方法，返回检查结果
           return limitCheck(ip);
       }
    }
    /**
     * 检查IP地址的请求是否超过了限制
     * 此方法用于控制特定IP地址的请求频率，以防止过度使用服务
     * 它通过在Redis中跟踪每个IP的请求计数来实现
     *
     * @param ip 请求者的IP地址，用于标识请求来源
     * @return boolean 如果请求没有超过限制，则返回true；否则返回false
     */
    private boolean limitCheck(String ip){

        // 检查是否存在该IP的请求计数
        if(Boolean.TRUE.equals(template.hasKey(Const.FLOW_LIMIT_COUNT + ip))){
            // 增加请求计数
            Long increment = Optional.ofNullable(template.opsForValue().increment(Const.FLOW_LIMIT_COUNT + ip)).orElse((0L));
            // 检查增加后的请求计数是否超过了限制
            if(increment > 100){
                log.info("IP: " + ip + "访问过于频繁");
                // 如果超过限制，设置阻止访问的标志，并返回false
                template.opsForValue().set(Const.FLOW_BLOCK + ip, "", 30, TimeUnit.SECONDS);
                return false;
            }
        }
        else{
            // 如果不存在请求计数，设置初始计数，并设置过期时间
            template.opsForValue().set(Const.FLOW_LIMIT_COUNT + ip, "1", 3, TimeUnit.SECONDS);

        }
        // 如果请求计数没有超过限制，返回true
        return true;


    }
}
